package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.apis.article.IArticleClient;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.exception.CustomException;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.mapper.SensitiveMapper;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsAutoScanService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 文章自动审核
 */
@Service
@Slf4j
public class WmNewsAutoScanServiceImpl  implements WmNewsAutoScanService {

    @Autowired
    WmNewsMapper wmNewsMapper;
    @Autowired
    GreenTextScan greenTextScan;
    @Autowired
    GreenImageScan greenImageScan;
    @Autowired
    FileStorageService fileStorageService;
    @Autowired
    IArticleClient iArticleClient;
    @Autowired
    WmUserMapper wmUserMapper;
    @Autowired
    WmChannelMapper wmChannelMapper;

    @Override
//    @Async//异步  需要结合@EnableAsync  //开启异步调用
    @Transactional
    public void autoScanWnNews(Integer id) {
        //让当前线程休眠3秒然后在执行审核逻辑
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //线程名称: task-1
        log.info("线程名称: {},   开始审核文章   id:{}",  Thread.currentThread().getName(),  id);


        //1.校验
        if(id==null){
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }
        WmNews wmNews = wmNewsMapper.selectById(id);
        if(wmNews==null){
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //校验状态  为1才需要审核
        if(wmNews.getStatus()==null || !wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())){
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }

        /**
         * 抽取文本和图片
         * {
         *     "content":"xxxxx标题和内容中所有的文本拼接后的字符串",
         *     "images:[xxxxx.jpg,  xxxx.jpg,   xxx.jpg]
         * }
         */
        Map<String, Object> map = handleTextAndImages(wmNews);

        //调用阿里云审核文本之前，先走自身的敏感词查询
        boolean senstiveResult = handleSensitiveScan((String)map.get("content"), wmNews);//返回false代表审核失败
        if(!senstiveResult){
            log.info("自管理敏感词审核失败，不在调用阿里云审核");
            return;
        }

        //2.调用阿里云 审核文本（标题和内容拼接到一块  调用阿里云调一次 扣一次钱）
        boolean textScanResult = handleTextScan((String)map.get("content"), wmNews);//返回false代表审核失败
        if(!textScanResult){
            log.info("调用阿里云审核文本失败");
            return;
        }

        //3.调用阿里云 审核图片（封面和内容中的图片  去重之后调用）
        boolean imageScanResult = handleImageScan((List<String>)map.get("images"), wmNews);//返回false代表审核失败
        if(!imageScanResult){
            log.info("图文识别出敏感内容或者调用阿里云审核图片失败");
            return;
        }


        //4.通过feign调用文章服务 保存文章ap_article  文章配置ap_aritcle_config  文章内容ap_article_content
        ResponseResult responseResult = saveAppArticle(wmNews);
        log.info("通过feign调用article服务  返回结果: {}", JSON.toJSONString(responseResult));
        if(responseResult==null){
            throw new RuntimeException("通过feign调用article服务  返回空");
        }
        if(responseResult.getCode()==null || !responseResult.getCode().equals(AppHttpCodeEnum.SUCCESS.getCode())){
            throw new RuntimeException("通过feign调用article服务  返回code 不是200");
        }


        //5.审核通过后修改文章状态为9（已发布） 回填wm_news表中article_id字段
        wmNews.setArticleId(Long.valueOf((String)responseResult.getData()));
        updateWmNews(wmNews, 9, "审核通过!!!");
    }

    @Autowired
    SensitiveMapper sensitiveMapper;
    /**
     * 项目启动后查询所有的敏感词 并初始化dfa 的 map结构
     */
    @PostConstruct//当前类实例化之后 属性注入之后 就会运行
    public void initSensitivesWord(){
        log.info("初始化敏感词列表到DFA的map结构中");
        List<WmSensitive> wmSensitives = sensitiveMapper.selectList(null);
        List<String> stringList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
        SensitiveWordUtil.initMap(stringList);
    }
    /**
     * 自管理敏感词审核
     * @param content
     * @param wmNews
     * @return
     */
    private boolean handleSensitiveScan(String content, WmNews wmNews) {
        boolean flag=true;
        //调用工具类查找content中是否包含敏感词
        Map<String, Integer> stringIntegerMap = SensitiveWordUtil.matchWords(content);
        if(stringIntegerMap!=null && stringIntegerMap.size()>0){
            //如果包含将文章状态修改为2 审核失败
            updateWmNews(wmNews, 2, "自管理敏感词审核失败，文章中存在敏感内容");
            flag=false;
        }
        return flag;
    }

    /**
     * 通过feign调用article保存文章
     * @param wmNews
     * @return
     */
    private ResponseResult saveAppArticle(WmNews wmNews) {
        ArticleDto dto = new ArticleDto();
        //1.将wmNews对象中属性值 封装到 ArticleDto中(有些属性值，wmNews中没有，需要单独查询db)
        //属性的拷贝
        BeanUtils.copyProperties(wmNews, dto);
        //文章的布局
        dto.setLayout(wmNews.getType());
        //频道
        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
        if (wmChannel != null) {
            dto.setChannelName(wmChannel.getName());
        }

        //作者
        dto.setAuthorId(wmNews.getUserId().longValue());
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
        if (wmUser != null) {
            dto.setAuthorName(wmUser.getName());
        }

        //设置文章id
        if (wmNews.getArticleId() != null) {
            dto.setId(wmNews.getArticleId());
        }else{
            dto.setId(null);
        }
        dto.setCreatedTime(new Date());


        //2.发起feign远程调用
        ResponseResult responseResult = iArticleClient.saveArticle(dto);
        return responseResult;
    }

    @Autowired
    Tess4jClient tess4jClient;
    /**
     * 审核图片
     * @param images
     * @param wmNews
     * @return  fasle代表审核失败有违规图片     true代表跳过审核或者图片没有违规
     */
    private boolean handleImageScan(List<String> images, WmNews wmNews) {
        //1.校验
        if(images==null || images.size()==0){
            return true;
        }

        List<byte[]> imageList = new ArrayList<byte[]>();//图片字节数组  集合
        try {
            for (String image : images) {
                byte[] bytes = fileStorageService.downLoadFile(image);

                //图文识别  开始
                ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
                BufferedImage bufferedImage = ImageIO.read(inputStream);
                String doOCRResult = tess4jClient.doOCR(bufferedImage);
                log.info("图文识别出来的文本：{}", doOCRResult);
                if(StringUtils.isNotBlank(doOCRResult)){
                    boolean sensitiveScan = handleSensitiveScan(doOCRResult, wmNews);
                    //如果返回false代表图文识别 出敏感内容
                    if(!sensitiveScan){
                        return sensitiveScan;
                    }
                }
                //图文识别  结束

                imageList.add(bytes);
            }
        } catch (Exception exception) {
            log.info("审核图片 从minio下载图片时 报错了", exception);
            updateWmNews(wmNews, 3, "审核图片 从minio下载图片时 报错了");
            return false;
        }

        //2.调用阿里云审核图片
        try {
            //{suggestion=block, label=contraband}
            Map map = greenImageScan.imageScan(imageList);
            if(map!=null){
                String suggestion = (String)map.get("suggestion");
                //2.如果阿里云返回block 修改文章状态为2审核失败  如果返回review修改文章状态为3需要人工审核
                if("block".equals(suggestion)){
                    //修改文章状态2  审核失败
                    updateWmNews(wmNews, 2, "文章中包含敏感图片，禁止公开");
                    return false;
                }else if("review".equals(suggestion)){
                    //修改文章状态为3   需要人工审核
                    updateWmNews(wmNews, 3, "文章中包含未知图片，需要人工审核");
                    return false;
                }else{
                    //审核通过
                    return true;
                }
            }
        } catch (Exception exception) {
            log.info("调用阿里云接口失败", exception);
        }
        //如果调用阿里云失败了 修改文章状态为3 人工审核
        updateWmNews(wmNews, 3, "调用阿里云失败，需要人工审核");
        return false;
    }

    /**
     * 调用阿里云审核文本
     * @param content 文本
     * @param wmNews
     * @return false代表审核失败  文章禁止公开         true代表跳过文本审核或者文本审核通过
     */
    private boolean handleTextScan(String content, WmNews wmNews) {
        //1.调用阿里云
        try {
            //{suggestion=block, label=contraband}
            Map map = greenTextScan.greeTextScan(content);
            if(map!=null){
                String suggestion = (String)map.get("suggestion");
                //2.如果阿里云返回block 修改文章状态为2审核失败  如果返回review修改文章状态为3需要人工审核
                if("block".equals(suggestion)){
                    //修改文章状态2  审核失败
                    updateWmNews(wmNews, 2, "文章中包含敏感内容，限制公开");
                    return false;
                }else if("review".equals(suggestion)){
                    //修改文章状态为3   需要人工审核
                    updateWmNews(wmNews, 3, "文章中包含未知内容，需要人工审核");
                    return false;
                }else{
                    //审核通过
                    return true;
                }
            }
        } catch (Exception exception) {
            log.info("调用阿里云接口失败", exception);
        }
        //如果调用阿里云失败了 修改文章状态为3 人工审核
        updateWmNews(wmNews, 3, "调用阿里云失败，需要人工审核");
        return false;
    }

    /**
     * 修改文章状态
     * @param wmNews
     * @param status 状态值
     * @param reason 原因
     */
    private void updateWmNews(WmNews wmNews, int status, String reason) {
        wmNews.setStatus((short)status);
        wmNews.setReason(reason);
        wmNewsMapper.updateById(wmNews);
    }

    /**
     * 抽取所有文本和图片
     * @param wmNews
     * @return
     */
    private Map<String, Object> handleTextAndImages(WmNews wmNews) {
        StringBuilder sb = new StringBuilder();//用于存储标题和内容中的文本
        Set<String> urls = new HashSet<>();//用于存储封面和内容中的图片  去重

        //处理content内容中的文本和图片
        if(StringUtils.isNotBlank(wmNews.getContent())){
            List<Map> mapList = JSON.parseArray(wmNews.getContent(), Map.class);
            for (Map map : mapList) {
                if(map.get("type").equals("text")){
                    sb.append((String)map.get("value"));
                }
                if(map.get("type").equals("image")){
                    urls.add((String)map.get("value"));
                }
            }
        }
        //处理title
        if(StringUtils.isNotBlank(wmNews.getTitle())){
            sb.append(wmNews.getTitle());
        }
        //处理封面图片
        if(StringUtils.isNotBlank(wmNews.getImages())){
            String[] split = wmNews.getImages().split(",");
            urls.addAll(Arrays.asList(split));
        }
        //封装结果返回
        Map<String, Object> ret = new HashMap<>();
        ret.put("content", sb.toString());
        ret.put("images", new ArrayList<String>(urls));
        return ret;
    }
}
